#include "trace/smart.hpp"

/**
 * \file
 * \ingroup trace
 * \brief cas::TraceSmart implementation.
 */

namespace cas
{
    SLOG_COMPONENT_DEFINE("TraceSmartComponents");

    uint64_t _getGlobalBlockSize()
    {
        std::string bs;
        GlobalValue::GetValueByName("traceSmart.blockSize", bs);
        return convert::dataSizeToSizeType(bs);
    }

    bool parseReadWrite(const toml::value &val)
    {
        if (val.is_boolean())
        {
            return val.as_boolean();
        }
        else if (val.is_string())
        {
            std::string str = val.as_string();
            std::transform(str.begin(), str.end(), str.begin(), ::tolower);
            return ("w" == str) || ("write" == str);
        }
        SFATAL_ERROR_CONT(__func__ << " invalid toml rw '" << val << '\'');
        return SmartRead;
    }

    // BlockTask
    BlockTask::BlockTask()
    {
        SLOG_FUNCTION_NOARGS();
        offset = 0;
        size = 1;
        bs = _getGlobalBlockSize(); // automatic block size;
        rw = SmartRead;

        genType = TASKGEN_DATA; // only generate data stream
        curr = (uint64_t)-1;
        status = TASK_INIT;
    }

    BlockTask::BlockTask(uint64_t offset, uint64_t size, uint32_t bs, bool rw)
    {
        SLOG_FUNCTION(offset << size << bs << (rw ? 'w' : 'r'));
        this->offset = offset;
        SASSERT_MSG(size > 0, "Invalid BlockTask's size");
        this->size = size;
        this->bs = bs;
        this->rw = rw;

        genType = TASKGEN_DATA; // only generate data stream
        curr = (uint64_t)-1;
        status = TASK_INIT;
    }

    inline SmartIOData BlockTask::next()
    {
        // no SLOG here for performance
        if (status == TASK_INIT)
        {
            if ((genType & TASKGEN_META) && ((uint64_t)-1 == curr))
            {
                // generate a Open request first
                curr = offset;
                lastValue = SmartIOMetadata(offset, SmartIOMetadata::SmartIOOpen);
                return lastValue;
            }
            curr = offset;
            if (size > bs)
            {
                SLOG_FUNCTION(typeName() << "TASK_INIT -> TASK_RUN");
                status = TASK_RUN;
                lastValue = SmartIOData(curr, bs, rw);
            }
            else
            {
                SLOG_FUNCTION(typeName() << "TASK_INIT -> TASK_END");
                status = TASK_END; // only genereate once
                lastValue = SmartIOData(curr, size, rw);
            }
        }
        else if (status == TASK_RUN)
        {
            curr += bs;
            if (curr + bs >= offset + size)
            {
                status = TASK_END;
                uint64_t lastBs = offset + size - curr;
                lastValue = SmartIOData(curr, lastBs, rw);
            }
            else
            {
                lastValue = SmartIOData(curr, bs, rw);
            }
        }
        // just retrun last value for TASK_PAUSE and TASK_END
        return lastValue;
    }

    inline uint32_t BlockTask::remains() const
    {
        if (unlikely(status == TASK_END))
        {
            return 0;
        }
        else if (unlikely(status == TASK_INIT))
        {
            return size / bs + bool(size % bs);
        }
        else
        {
            uint64_t remainSize = offset + size - curr;
            return remainSize / bs + bool(remainSize % bs);
        }
    }

    BlockTask::SharedPtr BlockTask::makeFromToml(const toml::value &config, const std::string &logPrefix)
    {
        uint64_t offset;
        uint64_t size;
        uint32_t bs = _getGlobalBlockSize();
        bool rw = SmartRead;

        SASSERT_MSG(config.contains("offset"), "Must provide BlockTask's offset");
        convert::tomlDataSizeToSizeType(offset, config.at("offset"));
        SASSERT_MSG(config.contains("size"), "Must provide BlockTask's size");
        convert::tomlDataSizeToSizeType(size, config.at("size"));
        if (config.contains("bs"))
        {
            convert::tomlDataSizeToSizeType(bs, config.at("bs"));
        }
        if (config.contains("rw"))
        {
            rw = parseReadWrite(config.at("rw"));
        }

        SLOG_INFO(logPrefix << "├── offset = " << offset);
        SLOG_INFO(logPrefix << "├── size   = " << size);
        SLOG_INFO(logPrefix << "├── bs     = " << bs);
        SLOG_INFO(logPrefix << "└── rw     = " << (rw ? 'w' : 'r'));

        const SharedPtr ptr(new BlockTask(offset, size, bs, rw));
        return ptr;
    }

    // RandomTask
    RandomTask::RandomTask()
    {
        SLOG_FUNCTION_NOARGS();
        offset = 0;
        size = 1;
        num = 2;
        repeat = 1;
        bs = _getGlobalBlockSize();
        rw = SmartRead;
        reset();
    }

    RandomTask::RandomTask(uint64_t offset, uint64_t size, uint32_t num, uint32_t repeat, uint32_t bs, bool rw)
    {
        SLOG_FUNCTION(offset << size << num << repeat << bs << (rw ? 'w' : 'r'));
        this->offset = offset;
        SASSERT_MSG(size > 0, "Invalid RandomTask's size");
        this->size = size;
        SASSERT_MSG(num > 0, "Invalid RandomTask's num");
        this->num = num;
        SASSERT_MSG(repeat > 0, "Invalid RandomTask's repeat");
        this->repeat = repeat;
        SASSERT_MSG(bs > 0, "Invalid RandomTask's bs");
        this->bs = bs;
        this->rw = rw;
        reset();
    }

    void RandomTask::reset()
    {
        SLOG_FUNCTION_NOARGS();
        auto rndPtr = std::make_shared<UniformInt64RND>();
        rnd = static_cast<RndPtr>(rndPtr);

        const uint64_t blockSize = _getGlobalBlockSize();
        rndPtr->setParam(-256 * blockSize, 256 * blockSize);

        genType = TASKGEN_DATA; // only generate data stream
        status = TASK_INIT;
        currSection = 0;
        currRepeat = 0;
        currBaseOffset = offset;
        currOffset = (uint64_t)-1;
    }

    inline void RandomTask::jumpRandom()
    {
        const int64_t randomOffset = (*rnd)();
        SLOG_FUNCTION(randomOffset);
        if (randomOffset >= 0)
        {
            currBaseOffset += randomOffset;
        }
        else
        {
            currBaseOffset -= (uint64_t)-randomOffset;
        }
    }

    inline SmartIOData RandomTask::next()
    {
        // no SLOG here for performance
        if (unlikely(status == TASK_INIT))
        {
            // do not care about metadata here
            currSection = 0;
            currRepeat = 0;
            currBaseOffset = offset;
            currOffset = offset;
            status = TASK_RUN;

            if (likely(size > bs))
            {
                lastValue = SmartIOData(currOffset, bs, rw);
                currOffset += bs;
            }
            else
            {
                // size <= bs, only one request for each section
                SLOG_FUNCTION(typeName() << "TASK_INIT -> next section");
                lastValue = SmartIOData(currOffset, size, rw);
                if (++currRepeat >= repeat)
                {
                    // repeat == 1
                    jumpRandom(); // next section
                    currRepeat = 0;
                    currOffset = currBaseOffset;
                    if (++currSection >= num)
                    {
                        SLOG_FUNCTION(typeName() << "TASK_INIT -> next section -> TASK_END");
                        status = TASK_END; // num == 1
                    }
                }
            }
        }
        else if (status == TASK_RUN)
        {
            if (likely(size > bs))
            {
                // size > bs, request multiple times
                if (unlikely(currOffset + bs >= currBaseOffset + size))
                {
                    uint64_t lastBs = currBaseOffset + size - currOffset;
                    lastValue = SmartIOData(currOffset, lastBs, rw);

                    currOffset = currBaseOffset; // act a repeat
                    if (++currRepeat >= repeat)
                    {
                        // repeat done, next section
                        jumpRandom();
                        currRepeat = 0;
                        currOffset = currBaseOffset;
                        if (++currSection >= num)
                        {
                            // section done, task end
                            status = TASK_END;
                        }
                    }
                }
                else
                {
                    lastValue = SmartIOData(currOffset, bs, rw);
                    currOffset += bs;
                }
            }
            else
            {
                // size <= bs, only one request for each section
                lastValue = SmartIOData(currOffset, size, rw);
                if (++currRepeat >= repeat)
                {
                    // repeat done, next section
                    jumpRandom(); // next section
                    currRepeat = 0;
                    currOffset = currBaseOffset;
                    if (++currSection >= num)
                    {
                        status = TASK_END; // num == 1
                    }
                }
            }
        }
        // just retrun last value for TASK_PAUSE and TASK_END
        return lastValue;
    }

    RandomTask::SharedPtr RandomTask::makeFromToml(const toml::value &config, const std::string &logPrefix)
    {
        uint64_t offset;
        uint64_t size;
        uint32_t num = 2;
        uint32_t repeat = 1;
        uint32_t bs = _getGlobalBlockSize();
        bool rw = SmartRead;

        SASSERT_MSG(config.contains("offset"), "Must provide BlockTask's offset");
        convert::tomlDataSizeToSizeType(offset, config.at("offset"));
        SASSERT_MSG(config.contains("size"), "Must provide BlockTask's size");
        convert::tomlDataSizeToSizeType(size, config.at("size"));
        if (config.contains("num"))
        {
            convert::tomlDataSizeToSizeType(num, config.at("num"));
        }
        if (config.contains("repeat"))
        {
            convert::tomlDataSizeToSizeType(repeat, config.at("repeat"));
        }
        if (config.contains("bs"))
        {
            convert::tomlDataSizeToSizeType(bs, config.at("bs"));
        }
        if (config.contains("rw"))
        {
            rw = parseReadWrite(config.at("rw"));
        }

        SLOG_INFO(logPrefix << "├── offset = " << offset);
        SLOG_INFO(logPrefix << "├── size   = " << size);
        SLOG_INFO(logPrefix << "├── num    = " << num);
        SLOG_INFO(logPrefix << "├── repeat = " << repeat);
        SLOG_INFO(logPrefix << "├── bs     = " << bs);
        SLOG_INFO(logPrefix << "└── rw     = " << (rw ? 'w' : 'r'));

        const SharedPtr ptr(new RandomTask(offset, size, num, repeat, bs, rw));
        return ptr;
    }

    // MixTask
    MixTask::MixTask(MixPolicy policy)
    {
        SLOG_FUNCTION(policy);
        this->policy = policy;
        status = TASK_END;
        tasks.clear();
        initPolicy();
    }

    MixTask::MixTask(const char *policyName)
    {
        SLOG_FUNCTION(policyName);
        std::string str;
        for (const char *c = policyName; *c; c++)
        {
            str += std::tolower(*c);
        }

        if (str == "series")
        {
            this->policy = MixSeries;
        }
        else if (str == "interweave")
        {
            this->policy = MixInterweave;
        }
        else if (str == "random")
        {
            this->policy = MixRandom;
        }
        else
        {
            SFATAL_ERROR("Unknown MixTask policy " << policyName);
        }
        status = TASK_END;
        tasks.clear();
        initPolicy();
    }

    void MixTask::initPolicy()
    {
        switch (this->policy)
        {
        case MixSeries:
            nextMixFunc = &MixTask::nextMixSeries;
            break;
        case MixInterweave:
            nextMixFunc = &MixTask::nextMixInterweave;
            steps.clear();
            timestampStep = false;
            break;
        case MixRandom:
            nextMixFunc = &MixTask::nextMixRandom;
            dist.clear();
            break;
        default:
            SFATAL_ERROR_CONT("Unknown MixTask policy");
        }
    }

    MixTask::~MixTask()
    {
        tasks.clear();
        steps.clear();
        dist.clear();
    }

    inline SmartIOData MixTask::next()
    {
        // no SLOG here for performance
        if (likely(status == TASK_RUN))
        {
            lastValue = (this->*nextMixFunc)();
        }
        else if (status == TASK_INIT)
        {
            status = TASK_RUN;
            currTaskIdx = 0;
            currTaskSteps = 0;
            currTask = tasks[0];
            lastValue = (this->*nextMixFunc)();
            if (currTask->isEnd())
            {
                status = TASK_END;
            }
        }
        return lastValue;
    }

    inline SmartIOData MixTask::nextMixSeries()
    {
        // no SLOG here for performance
        SmartIOData res;
        if (likely(!currTask->isEnd()))
        {
            res = currTask->next();
            currTaskSteps++;
            if (unlikely(currTask->isEnd()))
            {
                if (++currTaskIdx < tasks.size())
                {
                    currTask = tasks[currTaskIdx];
                    currTaskSteps = 0;
                    SLOG_FUNCTION(currTaskIdx << currTask->typeName());
                }
                else
                {
                    status = TASK_END; // all tasks finished
                }
            }
            return res;
        }
        else
        {
            SLOG_FUNCTION("isEnd" << currTaskIdx);
            if (++currTaskIdx < tasks.size())
            {
                currTask = tasks[currTaskIdx];
                currTaskSteps = 0;
                SLOG_FUNCTION(currTaskIdx << currTask->typeName());
                // execute first step of next task
                return nextMixSeries();
            }
            else
            {
                status = TASK_END; // all tasks finished
            }
            return lastValue;
        }
    }

    inline SmartIOData MixTask::nextMixInterweave()
    {
        // no SLOG here for performance
        return lastValue;
    }

    inline SmartIOData MixTask::nextMixRandom()
    {
        // no SLOG here for performance
        return lastValue;
    }

    inline SmartIOData MixTask::nextMixTrace()
    {
        // no SLOG here for performance
        return lastValue;
    }

    void MixTask::pushBack(const SmartBaseTaskPtr task)
    {
        const std::string taskStr = task->typeName() + (" @tasks[" + std::to_string(tasks.size()) + "]");
        SLOG_FUNCTION(taskStr);
        tasks.push_back(task);
    }

    void MixTask::pushEnd()
    {
        SLOG_FUNCTION_NOARGS();
        status = TASK_INIT;
    }

    MixTask::SharedPtr MixTask::makeFromToml(const toml::value &config, const std::string &logPrefix)
    {
        SLOG_FUNCTION_NOARGS();

        SASSERT_MSG(config.contains("policy"), "Must provide MixTask's policy");
        SASSERT_MSG(config.contains("tasks"), "Must provide MixTask's sub-tasks");
        const std::string policy = config.at("policy").as_string();

        const SharedPtr ptr(new MixTask(policy.c_str()));

        SLOG_INFO(logPrefix << "├── policy = " << policy);

        // @TODO
        uint64_t size = 0xabcd;
        switch (ptr->policy)
        {
        case MixInterweave:
            SLOG_INFO(logPrefix << "├── steps  = " << size);
            break;
        case MixRandom:
            SLOG_INFO(logPrefix << "├── dist   = " << size);
            break;
        default:
            break;
        }

        // get rootTask array
        const auto tasks = toml::find<toml::array>(config, "tasks");
        size_t n = tasks.size();
        SLOG_INFO(logPrefix << "└── tasks(" << n << ")");

        std::string type;

        for (const auto &t : tasks)
        {
            // t: each task in toml [[tasks]]
            const auto typeExpect = toml::expect<std::string>(t.at("type"));
            if (!typeExpect.is_ok())
            {
                SFATAL_ERROR_CONT(typeExpect.unwrap_err());
                continue;
            }
            type = typeExpect.unwrap();

            n--;
            SLOG_INFO(logPrefix << "    " << (n ? "├" : "└") << "── type = " << type << " #" << (tasks.size() - n - 1));
            std::string logPrefixNext = logPrefix + "    " + (n ? "│" : " ") + "   ";

            if ("block" == type)
            {
                auto subPtr = BlockTask::makeFromToml(t, logPrefixNext);
                ptr->pushBack(subPtr);
            }
            else if ("mix" == type)
            {
                auto subPtr = MixTask::makeFromToml(t, logPrefixNext);
                ptr->pushBack(subPtr);
            }
            else if ("random" == type)
            {
                auto subPtr = RandomTask::makeFromToml(t, logPrefixNext);
                ptr->pushBack(subPtr);
            }
            else
            {
                SFATAL_ERROR("type not support " << type);
            }
        }
        ptr->pushEnd();

        return ptr;
    }
}
